﻿#include <QHostInfo>
#include <QNetworkInterface>
#include <QUrl>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QEventLoop>
#include <QTimer>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonParseError>
#include <QSettings>
#include <QCoreApplication>
#include <QFileIconProvider>
#include <QTemporaryFile>
#include <QDir>
#include <QDesktopServices>
#include <QProcess>

#include <Windows.h>
#include <Shlobj.h>
#include <tlhelp32.h>

#include "qwindowsmanager.h"


/*!
    Returns the computer name of local machine.
*/
QString QWindowsManager::hostName()
{
    QString hostName;

    DWORD size=0;
    GetComputerName(NULL,&size);
    wchar_t *name=new wchar_t[size];

    GetComputerName(name,&size);
    hostName = QString::fromWCharArray(name);

    delete [] name;
    return hostName;
}

/*!
    Returns the user name of the currently logged in.
*/
QString QWindowsManager::userName()
{
    QString usrName;

    DWORD size=0;
    GetUserName(NULL,&size);
    wchar_t *name=new wchar_t[size];

    GetUserName(name,&size);
    usrName = QString::fromWCharArray(name);

    delete [] name;
    return usrName;
}

/*!
    Returns the wide area network IP of active networkInterface
    of this machine.

    \note If the native IP is automatically assigned,
    the return value of this function may be different.
*/
QString QWindowsManager::wanIp()
{
    QEventLoop loop;
    QTimer timer;

    QUrl url =QUrl::fromUserInput("http://whois.pconline.com.cn/ipJson.jsp?json=true");
    QNetworkRequest request(url);
    QNetworkAccessManager netManager;
    QNetworkReply *replay =  netManager.get(request);

    QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
    QObject::connect(replay, &QNetworkReply::finished, &loop, &QEventLoop::quit);

    timer.start(2000);
    loop.exec();

    if(replay->isFinished())
    {
        if(replay->error()==QNetworkReply::NoError)
        {
            QByteArray bytes = replay->readAll();
            QJsonParseError error;
            //Note that the encoding of the return value is ANSI,
            //but the QJSONDocument parses the data in UTF-8 encoding
            QJsonDocument jsonDoc = QJsonDocument::fromJson(QString::fromLocal8Bit(bytes).toUtf8(),&error);
            if(error.error==QJsonParseError::NoError)
            {
                return jsonDoc.object().value("ip").toString();
            }
            else
            {
                qDebug()<<__FUNCTION__<<" : parse replay error,"<<error.errorString();
                return "";
            }
        }
        else
        {
            qDebug()<<__FUNCTION__<<" : request error,"<<replay->errorString();
            return "";
        }
    }
    else
    {
        qDebug()<<__FUNCTION__<<" : request timeout";
        return "";
    }
}

/*!
    Returns the local area network IP of active networkInterface
    of this machine.
*/
QString QWindowsManager::lanIp()
{
    QString ip;

    QList<QNetworkAddressEntry> addressList = activeNetworkInterface().addressEntries();

    for(int i=0;i<addressList.size();i++)
    {
        if(addressList.at(i).ip().protocol() == QAbstractSocket::IPv4Protocol)
        {
            ip =addressList.at(i).ip().toString();
        }
    }

    return ip;
}

/*
    Returns the mac address of active networkInterface of this machine.
*/
QString QWindowsManager::mac()
{
    return activeNetworkInterface().hardwareAddress();
}

/*
    Returns the current active NetworkInterface of this machine.
*/
QNetworkInterface QWindowsManager::activeNetworkInterface()
{
    QNetworkInterface netInterface;
    QList<QNetworkInterface> nets = QNetworkInterface::allInterfaces();
    int nCnt = nets.count();

    // Also meet the following conditions is the active networkinterface
    // · the networkinterface is running
    // · the networkinterface is not loopback networkinterface.
    // · the hardwareAddress of networkinterface is not contains belows fileds：
    //  "00:05:69"; //vmware1
    //  "00:0C:29"; //vmware2
    //  "00:50:56"; //vmware3
    //  "00:1c:14"; //vmware4
    //  "00:1C:42"; //parallels1
    //  "00:03:FF"; //microsoft virtual pc
    //  "00:0F:4B"; //virtual iron 4
    //  "00:16:3E"; //red hat xen , oracle vm , xen source, novell xen
    //  "08:00:27"; //virtualbox
    for(int i = 0; i < nCnt; i ++)
    {
        if( nets[i].flags().testFlag(QNetworkInterface::IsUp)&&
                nets[i].flags().testFlag(QNetworkInterface::IsRunning)&&
                !nets[i].flags().testFlag(QNetworkInterface::IsLoopBack)&&
                !nets[i].hardwareAddress().contains("00:05:69")&&
                !nets[i].hardwareAddress().contains("00:0C:29")&&
                !nets[i].hardwareAddress().contains("00:50:56")&&
                !nets[i].hardwareAddress().contains("00:1c:14")&&
                !nets[i].hardwareAddress().contains("00:05:69")&&
                !nets[i].hardwareAddress().contains("00:1C:42")&&
                !nets[i].hardwareAddress().contains("00:05:69")&&
                !nets[i].hardwareAddress().contains("00:03:FF")&&
                !nets[i].hardwareAddress().contains("00:0F:4B")&&
                !nets[i].hardwareAddress().contains("00:16:3E")&&
                !nets[i].hardwareAddress().contains("08:00:27"))
        {
            netInterface = nets[i];
            break;
        }
    }

    return netInterface;
}

/*!
    Converts the windows error code to human-readable string and returns it.
*/
QString QWindowsManager::windowsErrorCodeToMessage(int errorCode)
{
    QString ret;
    wchar_t *string = 0;
    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
                  NULL,
                  errorCode,
                  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                  (LPWSTR)&string,
                  0,
                  NULL);
    ret = QString::fromWCharArray(string);
    LocalFree((HLOCAL)string);

    if (ret.isEmpty() && errorCode == ERROR_MOD_NOT_FOUND)
        ret = QString::fromLatin1("The specified module could not be found.");
    if (ret.endsWith(QLatin1String("\r\n")))
        ret.chop(2);
    if (ret.isEmpty())
        ret = QString::fromLatin1("Unknown error 0x%1.")
                .arg(unsigned(errorCode), 8, 16, QLatin1Char('0'));
    return ret;
}

/*!
    Associate all files with an extension to the application registered with
    the system, and returns true if the association succeeds, otherwise returns
    false. If \a cover is false, existing file associations will not be overridden

    \note You can view the \a progid for all applications that have been registered
    in the system in registry path HKEY_CURRENT_USER/Software/Classes.
*/
bool QWindowsManager::createFileAssociation(QString extension, QString progid,bool cover)
{
    QString rootPath ="HKEY_CURRENT_USER\\Software\\Classes";
    QSettings settings(rootPath,QSettings::NativeFormat);

    settings.beginGroup(progid);
    QString currentProgid = settings.value(".","NotExist").toString();
    settings.endGroup();
    if(currentProgid=="NotExist")
    {
        qDebug()<<__FUNCTION__<<" : Progid is not exist";
        return false;
    }

    settings.beginGroup(extension);
    QString currentExt= settings.value(".","NotExist").toString();
    settings.endGroup();
    if(currentExt!="NotExist" && !cover)
    {
        qDebug()<<__FUNCTION__<<" : Extension is already exist";
        return false;
    }

    //Associate the extension group to a progid group
    settings.beginGroup(extension);
    settings.setValue(".",progid);
    settings.endGroup();
    settings.sync();

    //Notifies the system that the file association has changed
    SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);

    return true;

}

/*!
    Create a new file association with application id \a progid, file description
    \a description, file icon \a iconPath, command to open the file \aopenCmd.
    Returns true if creation is successful, otherwise returns false. If \a cover
    is false, existing file associations will not be overridden.

    \note Must ensure that the progid is globally unique, which you can name
    using the program name. For example, QtProject.QtCreator.cpp.
*/
bool QWindowsManager::createFileAssociation(QString extension, QString progid, QString description,QString iconPath,QString openCmd, bool cover)
{
    QString rootPath ="HKEY_CURRENT_USER\\Software\\Classes";
    QSettings settings(rootPath,QSettings::NativeFormat);

    settings.beginGroup(extension);
    QString currentExt = settings.value(".","NotExist").toString();
    settings.endGroup();
    if(currentExt!="NotExist" && !cover)
    {
        qDebug()<<__FUNCTION__<<" : Extension is already exist";
        return false;
    }

    //Create an extension group, and associate the extension group to a progid group
    settings.beginGroup(extension);
    settings.setValue(".",progid);
    settings.endGroup();

    //Create an progid group, and set the description, icon, openCmd
    settings.beginGroup(progid);
    settings.setValue(".",description);
    settings.setValue("DefaultIcon/.",iconPath);
    settings.setValue("shell/Open/Command/.",openCmd);
    settings.endGroup();
    settings.sync();

    //Notifies the system that the file association has changed
    SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);

    return true;
}

/*!
    Creates a context menu item that will be displayed when the user click
    rightButton on the desktop.
*/
bool QWindowsManager::createContextMenuItem(QString itemId,QString description,QString iconPath, QString openCmd, bool cover)
{
    QString rootPath ="HKEY_CLASSES_ROOT\\Directory\\Background\\shell";
    QSettings settings(rootPath,QSettings::NativeFormat);

    settings.beginGroup(itemId);
    QString currentName = settings.value(".","NotExist").toString();
    settings.endGroup();
    if(currentName!="NotExist" && !cover)
    {
        qDebug()<<__FUNCTION__<<" : Menu item is already exist";
        return false;
    }

    //Create a menuitem group, and set the description, iconPath, openCmd
    settings.beginGroup(itemId);
    settings.setValue(".",description);
    settings.setValue("Icon",iconPath);
    settings.setValue("command/.",openCmd);
    settings.endGroup();
    settings.sync();

    return true;
}

/*!
    Remove a context menu item.
*/
void QWindowsManager::removeContextMenuItem(QString itemId)
{
    QString rootPath ="HKEY_CLASSES_ROOT\\Directory\\Background\\shell";
    QSettings settings(rootPath,QSettings::NativeFormat);

    settings.remove(itemId);
}

/*!
    Returns whether the program with the specified name \a appName is
    set to start automatically.
*/
bool QWindowsManager::autoStartEnabled(QString appName)
{
    QString rootPath = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
    QSettings settings(rootPath,QSettings::NativeFormat);

    QString openCmd = settings.value(appName,"NotExist").toString();

    if(openCmd=="NotExist")
    {
        return false;
    }
    else
    {
        return true;
    }
}

/*!
    Sets whether the program with the specified name \a appName is allowed
    to start automatically.
*/
void QWindowsManager::setAutoStartEnabled(QString appName,QString openCmd,bool on)
{
    QString rootPath = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
    QSettings settings(rootPath,QSettings::NativeFormat);

    if(on)
    {
        settings.setValue(appName,openCmd);
    }
    else
    {
        settings.remove(appName);
    }
}

/*!
    Returns the value of the environment variable width the specified
    name \a name.
*/
QStringList QWindowsManager::environmentVariableValue(QString name)
{
    QString rootPath ="HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment";
    QSettings settings(rootPath,QSettings::NativeFormat);

    QString strValue = settings.value(name).toString();

    return strValue.split(";");
}

/*!
    Overrides the environment variable with the specified
    name \a name and values \a values.
*/
void QWindowsManager::setEnvironmentVariableValue(QString name, QStringList values)
{
    QString rootPath ="HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment";
    QSettings settings(rootPath,QSettings::NativeFormat);

    QString strValue;
    for(int i=0;i<values.size();i++)
    {
        strValue.append(values.at(i)+";");
    }

    settings.setValue(name,strValue);
    settings.sync();

    //Send notifications to all programs
    ::SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
                         (LPARAM)"Environment", SMTO_ABORTIFHUNG,5000, NULL);
}

/*!
    Adds several values to the environment variable with the
    specified name \a name.
*/
void QWindowsManager::appendEnvironmentVariableValue(QString name, QStringList values)
{
    QString rootPath ="HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment";
    QSettings settings(rootPath,QSettings::NativeFormat);

    QString oldstrValue = settings.value(name).toString();
    QString strValue;
    for(int i=0;i<values.size();i++)
    {
        strValue.append(values.at(i)+";");
    }

    if(oldstrValue.right(1)!=";" && oldstrValue!=""){
        oldstrValue.append(";");
    }
    oldstrValue.append(strValue);

    settings.setValue(name,oldstrValue);
    settings.sync();

    //Send notifications to all programs
    ::SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
                         (LPARAM)"Environment", SMTO_ABORTIFHUNG,5000, NULL);
}

/*!

    Sends a Windows message to the specified window,and returns the
    return value of the message handler. \a WindowClassName and
    \a windowTitleName specifies the window to receive the message.
    You can use only one parameter and leave the other one blank.
    \a msgId specifies the windows message ID.\a data is passed to
    the target window as the LAPARM of the message.

    \note This function will block until the target window's message
    handler returns.Please destroy data properly after the function
    call is completed to avoid memory leaks

*/
long QWindowsManager::sendMessage(QString windowClassName,QString windowTitleName,unsigned int msgId,void *data)
{
    //Convert QString to wchart_t
    wchar_t *wstrWinClass;
    wchar_t *wstrWinTitle;
    if(windowClassName!="")
    {
        wstrWinClass = new wchar_t[windowClassName.length()];
        windowClassName.toWCharArray(wstrWinClass);
    }else{
        wstrWinClass=NULL;
    }
    if(windowTitleName!="")
    {
        wstrWinTitle = new wchar_t[windowTitleName.length()];
        windowTitleName.toWCharArray(wstrWinTitle);
    }else{
        wstrWinTitle=NULL;
    }

    //Find window handle
    HWND hwnd = ::FindWindow(wstrWinClass,wstrWinTitle);
    if(!hwnd)
    {
        delete [] wstrWinClass;
        delete [] wstrWinTitle;

        qDebug()<<__FUNCTION__<<": failed to find window";
        return -9999;
    }

    delete [] wstrWinClass;
    delete [] wstrWinTitle;

    //Block sending message
    return ::SendMessage(hwnd,msgId,NULL,(LPARAM)data);
}

/*!

    Sends a Windows message to the specified window,and return immediately.
    Returns true if the message was sent successfully,otherwise returns false.
    \a WindowClassName and \a windowTitleName specifies the window to receive
    the message.You can use only one parameter and leave the other one blank.
    \a msgId specifies the windows message ID.\a data is passed tothe target
    window as the LAPARM of the message.

    \note This function returns immediately after sending the message.Make
    sure the target window has received the data before destroying \a data.

*/
bool QWindowsManager::postMessage(QString windowClassName, QString windowTitleName, unsigned int msgId, void *data)
{
    //Convert QString to wchart_t
    wchar_t *wstrWinClass;
    wchar_t *wstrWinTitle;
    if(windowClassName!="")
    {
        wstrWinClass = new wchar_t[windowClassName.length()];
        windowClassName.toWCharArray(wstrWinClass);
    }else{
        wstrWinClass=NULL;
    }
    if(windowTitleName!="")
    {
        wstrWinTitle = new wchar_t[windowTitleName.length()];
        windowTitleName.toWCharArray(wstrWinTitle);
    }else{
        wstrWinTitle=NULL;
    }

    //Find window handle
    HWND hwnd = ::FindWindow(wstrWinClass,wstrWinTitle);
    if(!hwnd)
    {
        delete [] wstrWinClass;
        delete [] wstrWinTitle;

        qDebug()<<__FUNCTION__<<": failed to find window";
        return false;
    }

    delete [] wstrWinClass;
    delete [] wstrWinTitle;

    //Post the message to the message queue
    return ::PostMessage(hwnd,msgId,NULL,(LPARAM)data);
}

/*!
    Call the local system default program to open the URL \a url.
    The Url can be a web address, a local file, or an local executable.

    \note If the \a url is a reference to a local file, please use
    QUrl::fromLocalFile() to create the QUrl object. If the \a url path contains
    Chinese characters, please use QStringLiteral() to wrap the path string.
 */
bool QWindowsManager::openUrl(QUrl url)
{

    return QDesktopServices::openUrl(url);
}

/*!
    Kills the process with the specified PID \a processId. Returns
    true if successful, otherswise returns false.
*/
bool QWindowsManager::killProcess(int processId)
{
    bool ok=true;
    HANDLE hProcess=OpenProcess(PROCESS_TERMINATE,FALSE,processId);
    if(hProcess!=NULL)
    {
        if(!TerminateProcess(hProcess,0))
        {
            ok=false;
        }
    }else{
        ok = false;
    }

    ::CloseHandle(hProcess);
    return ok;
}

/*!
    Kills the process with the specified Name \a processName. Returns
    true if successful, otherswise returns false.
*/
bool QWindowsManager::killProcess(QString processName)
{
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(pe32);

    //Gets a snapshot of all current processes on the system
    HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if(hProcessSnap == INVALID_HANDLE_VALUE)
    {
        qDebug()<<__FUNCTION__<<": failed to get process snapshot";
        return false;
    }

    //Gets all processesId with the same Name as processName
    QVector<unsigned long> pidList;
    BOOL bMore = ::Process32First(hProcessSnap,&pe32);
    while(bMore)
    {
        QString pName = QString::fromWCharArray(pe32.szExeFile);
        unsigned long pId = pe32.th32ProcessID;

        if(pName.contains(processName))
        {
            pidList.push_back(pId);
        }

        bMore = ::Process32Next(hProcessSnap,&pe32);
    }
    ::CloseHandle(hProcessSnap);

    //Kill all the found processes
    if(pidList.size()>0)
    {
        bool ok = true;

        for(int i=0;i<pidList.size();i++)
        {
            HANDLE hProcess=OpenProcess(PROCESS_TERMINATE,FALSE,pidList.at(i));
            if(hProcess!=NULL)
            {
                if(!TerminateProcess(hProcess,0))
                {
                    ok=false;
                }
            }else{
                ok = false;
            }
            ::CloseHandle(hProcess);
        }

        return ok;
    }
    else
    {
        return false;
    }
}

/*!
    Returns the icon for the specified file \a filePath.
*/
QIcon QWindowsManager::getFileIcon(QString filePath)
{
    QFileIconProvider iconProvider;
    return iconProvider.icon(QFileInfo(filePath));
}

/*!
    Returns the icon for the specified file extension \a fileExtension.
*/
QIcon QWindowsManager::getFileIconWithExtension(QString fileExtension)
{
    QIcon icon;
    QFileIconProvider iconProvider;
    QTemporaryFile tmpFile(QDir::tempPath()+"XXXXXX" + fileExtension);
    tmpFile.setAutoRemove(false);

    if(tmpFile.open())
    {
        QString fileName = tmpFile.fileName();
        tmpFile.write(QByteArray());
        tmpFile.close();
        icon = iconProvider.icon(QFileInfo(fileName));
        tmpFile.remove();
    }
    else
    {
        qDebug()<<__FUNCTION__<<" : failed to create temporary file";
    }

    return icon;
}

